package lg.minecraft.plugin.pokemonmenu.gui;

import com.cobblemon.mod.common.api.Priority;
import com.cobblemon.mod.common.api.abilities.Ability;
import com.cobblemon.mod.common.api.abilities.PotentialAbility;
import com.cobblemon.mod.common.api.pokemon.Natures;
import com.cobblemon.mod.common.api.pokemon.PokemonProperties;
import com.cobblemon.mod.common.api.pokemon.stats.Stat;
import com.cobblemon.mod.common.api.pokemon.stats.Stats;
import com.cobblemon.mod.common.pokemon.*;
import lg.minecraft.plugin.cobblemoncore.util.BasePokemonUtil;
import lg.minecraft.plugin.cobblemoncore.util.BukkitNmsConverter;
import lg.minecraft.plugin.cobblemoncore.util.PokemonPropertiesArgumentType;
import lg.minecraft.plugin.cobblemoncore.util.PokemonUtil;
import lg.minecraft.plugin.library.inventory.CancelInventoryContext;
import lg.minecraft.plugin.library.inventory.ClickInventoryContext;
import lg.minecraft.plugin.library.inventory.InventoryContext;
import lg.minecraft.plugin.library.inventory.LGInventory;
import lg.minecraft.plugin.pokemonmenu.LGPokemonMenu;
import lg.minecraft.plugin.pokemonmenu.command.InputType;
import lg.minecraft.plugin.pokemonmenu.gui.edit.EditFormInventor;
import lg.minecraft.plugin.pokemonmenu.gui.edit.EditMoveInventor;
import lg.minecraft.plugin.pokemonmenu.gui.edit.EditNatureInventor;
import lg.minecraft.plugin.pokemonmenu.gui.edit.EditPokemonBallInventor;
import lg.minecraft.plugin.pokemonmenu.handler.input.PlayerInputHandler;
import lg.minecraft.plugin.pokemonmenu.handler.input.PlayerInputManager;
import lg.minecraft.plugin.pokemonmenu.manager.PokemonPlayerManger;
import net.minecraft.class_2561;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;

import java.util.*;

/**
 * @author langle__
 * @version 1.0
 */
public class EditInventor extends LGInventory implements PlayerInputHandler {

    private static final int[] GLASS_ARRAY = {0, 8, 9, 17, 18, 26, 27, 35, 36, 44, 45, 46, 47, 48, 50, 52, 53};

    private final Pokemon pokemon;
    private final List<PotentialAbility> abilities = new ArrayList<>();

    private int inputMode = 0;

    public EditInventor(Pokemon pokemon) {
        super(6, LGPokemonMenu.getMessage().getGuiTitleEdit());
        this.pokemon = pokemon;
        for (List<PotentialAbility> entry : pokemon.getForm().getAbilities().getMapping().values()) {
            abilities.addAll(entry);
        }
    }

    @Override
    public void onInventoryOpen(CancelInventoryContext context) {
        for (int slot : GLASS_ARRAY) inventory.setItem(slot, LGPokemonMenu.getGuiItem().getBorder());

        inventory.setItem(3, LGPokemonMenu.getGuiItem().getEditShiny(BasePokemonUtil.isShiny(pokemon)));
        inventory.setItem(5, LGPokemonMenu.getGuiItem().getEditGender(BasePokemonUtil.getGender(pokemon)));
        inventory.setItem(12, LGPokemonMenu.getGuiItem().getEditLevel(BasePokemonUtil.getLevel(pokemon)));
        inventory.setItem(14, LGPokemonMenu.getGuiItem().getEditScale(BasePokemonUtil.getScale(pokemon)));
        inventory.setItem(13, LGPokemonMenu.getGuiItem().getEditFriendship(BasePokemonUtil.getFriendship(pokemon)));

        inventory.setItem(10, LGPokemonMenu.getGuiItem().getEditIvAttack(BasePokemonUtil.getIvAttack(pokemon)));
        inventory.setItem(11, LGPokemonMenu.getGuiItem().getEditIvSpecialAttack(BasePokemonUtil.getIvSpecialAttack(pokemon)));
        inventory.setItem(19, LGPokemonMenu.getGuiItem().getEditIvDefence(BasePokemonUtil.getIvDefence(pokemon)));
        inventory.setItem(20, LGPokemonMenu.getGuiItem().getEditIvSpecialDefence(BasePokemonUtil.getIvSpecialDefence(pokemon)));
        inventory.setItem(28, LGPokemonMenu.getGuiItem().getEditIvHp(BasePokemonUtil.getIvHP(pokemon)));
        inventory.setItem(29, LGPokemonMenu.getGuiItem().getEditIvSpeed(BasePokemonUtil.getIvSpeed(pokemon)));

        inventory.setItem(1, LGPokemonMenu.getGuiItem().getIvsMin());
        inventory.setItem(2, LGPokemonMenu.getGuiItem().getIvsMax());
        inventory.setItem(6, LGPokemonMenu.getGuiItem().getEvsMax());
        inventory.setItem(7, LGPokemonMenu.getGuiItem().getEvsMin());

        int remainingEVs = BasePokemonUtil.getRemainingEVs(pokemon);
        inventory.setItem(15, LGPokemonMenu.getGuiItem().getEditEvAttack(BasePokemonUtil.getEvAttack(pokemon), remainingEVs));
        inventory.setItem(16, LGPokemonMenu.getGuiItem().getEditEvSpecialAttack(BasePokemonUtil.getEvSpecialAttack(pokemon), remainingEVs));
        inventory.setItem(24, LGPokemonMenu.getGuiItem().getEditEvDefence(BasePokemonUtil.getEvDefence(pokemon), remainingEVs));
        inventory.setItem(25, LGPokemonMenu.getGuiItem().getEditEvSpecialDefence(BasePokemonUtil.getEvSpecialDefence(pokemon), remainingEVs));
        inventory.setItem(33, LGPokemonMenu.getGuiItem().getEditEvHp(BasePokemonUtil.getEvHP(pokemon), remainingEVs));
        inventory.setItem(34, LGPokemonMenu.getGuiItem().getEditEvSpeed(BasePokemonUtil.getEvSpeed(pokemon), remainingEVs));

        inventory.setItem(21, LGPokemonMenu.getGuiItem().getEditNature(PokemonUtil.getNatureName(pokemon), PokemonUtil.getNatureType(pokemon)));
        inventory.setItem(32, LGPokemonMenu.getGuiItem().getEditPokemonBall(pokemon.getCaughtBall()));

        inventory.setItem(23, LGPokemonMenu.getGuiItem().getEditAbility(pokemon.getAbility()));
        inventory.setItem(30, LGPokemonMenu.getGuiItem().getEditForm());
        inventory.setItem(31, LGPokemonMenu.getGuiItem().getEditMove());

        inventory.setItem(40, LGPokemonMenu.getGuiItem().getEditNick(PokemonUtil.getNickName(pokemon)));
        inventory.setItem(22, LGPokemonMenu.getGuiItem().getEditPokemon(pokemon));
        inventory.setItem(51, LGPokemonMenu.getGuiItem().getInputEdit());

        inventory.setItem(49, LGPokemonMenu.getGuiItem().getBack());
    }

    @Override
    public void onInventoryClick(ClickInventoryContext context) {
        context.setCancelled(true);
        Player player = context.getPlayer();
        int slot = context.getSlot();
        ClickType click = context.getClick();

        if (!player.hasPermission("LGPokemonMenu.edit")) {
            player.closeInventory();
            return;
        }

        if (slot == 32) {
            new EditPokemonBallInventor(pokemon).openInventory(player);
            player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5F, 1F);
        } else if (slot == 49) {
            new PokemonInventory(pokemon).openInventory(player);
            player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5F, 1F);
        } else if (slot == 3) {
            boolean newShiny = !pokemon.getShiny();
            pokemon.setShiny(newShiny);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 5) {
            Gender gender = getGender(pokemon.getGender());
            pokemon.setGender(gender);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 23) {
            Ability ability = pokemon.getAbility();
            ability = cycleToNextAbility(ability);
            pokemon.setAbility$common(ability);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 12) {
            int level = pokemon.getLevel();
            int value = getBaseValue(click, level);
            pokemon.setLevel(Math.max(0, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 14) {
            pokemon.setScaleModifier(getScale(click));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 13) {
            int friendship = pokemon.getFriendship();
            int value = getBaseValue(click, friendship);
            pokemon.setFriendship(Math.max(0, value), true);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 21) {
            if (click.isShiftClick()) {
                Nature maxAndMinKeys = suggestNature();
                pokemon.setNature(maxAndMinKeys);
                onInventoryOpen(context);
                player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
            } else {
                new EditNatureInventor(pokemon).openInventory(player);
                player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5F, 1F);
            }
        } else if (slot == 10) {
            int value = BasePokemonUtil.getIvAttack(pokemon);
            pokemon.setIV(Stats.ATTACK, getIVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 11) {
            int value = BasePokemonUtil.getIvSpecialAttack(pokemon);
            pokemon.setIV(Stats.SPECIAL_ATTACK, getIVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 19) {
            int value = BasePokemonUtil.getIvDefence(pokemon);
            pokemon.setIV(Stats.DEFENCE, getIVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 20) {
            int value = BasePokemonUtil.getIvSpecialDefence(pokemon);
            pokemon.setIV(Stats.SPECIAL_DEFENCE, getIVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 28) {
            int value = BasePokemonUtil.getIvHP(pokemon);
            pokemon.setIV(Stats.HP, getIVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 29) {
            int value = BasePokemonUtil.getIvSpeed(pokemon);
            pokemon.setIV(Stats.SPEED, getIVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 15) {
            int value = BasePokemonUtil.getEvAttack(pokemon);
            pokemon.setEV(Stats.ATTACK, getEVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 16) {
            int value = BasePokemonUtil.getEvSpecialAttack(pokemon);
            pokemon.setEV(Stats.SPECIAL_ATTACK, getEVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 24) {
            int value = BasePokemonUtil.getEvDefence(pokemon);
            pokemon.setEV(Stats.DEFENCE, getEVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 25) {
            int value = BasePokemonUtil.getEvSpecialDefence(pokemon);
            pokemon.setEV(Stats.SPECIAL_DEFENCE, getEVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 33) {
            int value = BasePokemonUtil.getEvHP(pokemon);
            pokemon.setEV(Stats.HP, getEVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 34) {
            int value = BasePokemonUtil.getEvSpeed(pokemon);
            pokemon.setEV(Stats.SPEED, getEVsValue(click, value));
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 40) {
            if (click == ClickType.LEFT) {
                inputMode = 1;
                player.closeInventory();
                player.sendMessage(LGPokemonMenu.getMessage().getInputNickname());
                PlayerInputManager.waitForInput(player, this);
            } else if (click == ClickType.RIGHT) {
                pokemon.setNickname(null);
                openInventory(player);
            }
        } else if (slot == 30) {
            new EditFormInventor(pokemon).openInventory(player);
            player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5F, 1F);
        } else if (slot == 31) {
            new EditMoveInventor(pokemon).openInventory(player);
            player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5F, 1F);
        } else if (slot == 22) {
            Pokemon clonePokemon = pokemon.clone(true, BukkitNmsConverter.getRegistryAccess());
            PokemonPlayerManger.INSTANCE.getPokemonPlayer(player).addPartyPokemon(clonePokemon);
            player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 0.5F, 1F);
        } else if (slot == 1) {
            pokemon.setIV(Stats.HP, 0);
            pokemon.setIV(Stats.SPEED, 0);
            pokemon.setIV(Stats.ATTACK, 0);
            pokemon.setIV(Stats.SPECIAL_ATTACK, 0);
            pokemon.setIV(Stats.DEFENCE, 0);
            pokemon.setIV(Stats.SPECIAL_DEFENCE, 0);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 2) {
            pokemon.setIV(Stats.HP, 31);
            pokemon.setIV(Stats.SPEED, 31);
            pokemon.setIV(Stats.ATTACK, 31);
            pokemon.setIV(Stats.SPECIAL_ATTACK, 31);
            pokemon.setIV(Stats.DEFENCE, 31);
            pokemon.setIV(Stats.SPECIAL_DEFENCE, 31);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 6) {
            autoAllocateEVs();
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 7) {
            pokemon.setEV(Stats.HP, 0);
            pokemon.setEV(Stats.SPEED, 0);
            pokemon.setEV(Stats.ATTACK, 0);
            pokemon.setEV(Stats.SPECIAL_ATTACK, 0);
            pokemon.setEV(Stats.DEFENCE, 0);
            pokemon.setEV(Stats.SPECIAL_DEFENCE, 0);
            onInventoryOpen(context);
            player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0F, 1.8F);
        } else if (slot == 51) {
            inputMode = 2;
            player.closeInventory();
            player.sendMessage(LGPokemonMenu.getMessage().getInputPokemon());
            PlayerInputManager.waitForInput(player, InputType.POKEMON_PROPERTIES, this);
        }
    }

    @Override
    public void onInventoryClose(InventoryContext context) {
    }

    @Override
    public void onPlayerInput(Player player, String input) {
        if (inputMode == 1) {
            pokemon.setNickname(class_2561.method_43470(input.replace("&", "§")));
        } else if (inputMode == 2) {
            PokemonProperties properties = PokemonPropertiesArgumentType.INSTANCE.parse(input);
            properties.apply(pokemon);
        }
        openInventory(player);
        inputMode = 0;
    }

    private float getScale(ClickType click) {
        float scale = pokemon.getScaleModifier();
        if (click == ClickType.LEFT) {
            scale += 0.25F;
        } else if (click == ClickType.SHIFT_LEFT) {
            scale += 1F;
        } else if (click == ClickType.RIGHT) {
            scale -= 0.25F;
        } else if (click == ClickType.SHIFT_RIGHT) {
            scale -= 1F;
        }
        float scaleMin = LGPokemonMenu.getMainConfig().getScaleMin();
        float scaleMax = LGPokemonMenu.getMainConfig().getScaleMax();
        if (scale < scaleMin) {
            scale = scaleMin;
        } else if (scale > scaleMax) {
            scale = scaleMax;
        }
        return scale;
    }

    private int getIVsValue(ClickType click, int value) {
        return Math.min(IVs.MAX_VALUE, Math.max(0, getValue(click) + value));
    }

    private int getEVsValue(ClickType click, int value) {
        int remainingEVs = BasePokemonUtil.getRemainingEVs(pokemon);
        int addValue = getValue(click);
        int result = Math.min(addValue, remainingEVs);
        return Math.min(EVs.MAX_STAT_VALUE, Math.max(0, result + value));
    }

    private int getValue(ClickType click) {
        if (click == ClickType.LEFT) {
            return 1;
        } else if (click == ClickType.SHIFT_LEFT) {
            return 10;
        } else if (click == ClickType.RIGHT) {
            return -1;
        } else if (click == ClickType.SHIFT_RIGHT) {
            return -10;
        }
        return 0;
    }

    private int getBaseValue(ClickType click, int value) {
        if (click == ClickType.LEFT) {
            value += 1;
        } else if (click == ClickType.SHIFT_LEFT) {
            value += 10;
        } else if (click == ClickType.RIGHT) {
            value -= 1;
        } else if (click == ClickType.SHIFT_RIGHT) {
            value -= 10;
        }
        return value;
    }

    private Gender getGender(Gender gender) {
        if (gender == Gender.MALE) {
            return Gender.FEMALE;
        } else if (gender == Gender.FEMALE) {
            return Gender.MALE;
        }
        return gender;
    }

    public Ability cycleToNextAbility(Ability value) {
        PotentialAbility potential = null;
        for (int i = 0; i < abilities.size(); i++) {
            PotentialAbility temp = abilities.get(i);
            if (temp.getTemplate() == value.getTemplate()) {
                potential = abilities.get((i == abilities.size() - 1) ? 0 : i + 1);
                break;
            }
        }
        if (potential == null) return value;
        return potential.getTemplate().create(false, Priority.NORMAL);
    }

    /**
     * 判断该宝可梦的种族基础属性是否全部相同
     */
    private boolean hasUniformBaseStats() {
        HashMap<Stat, Integer> baseStats = pokemon.getSpecies().getBaseStats();
        Collection<Integer> values = baseStats.values();
        return values.stream().allMatch(v -> v.equals(values.iterator().next()));
    }

    /**
     * 自动根据基础属性分配努力值（EV）
     */
    public void autoAllocateEVs() {
        HashMap<Stat, Integer> baseStats = pokemon.getSpecies().getBaseStats();

        if (hasUniformBaseStats()) {
            int average = EVs.MAX_TOTAL_VALUE / baseStats.size();
            baseStats.keySet().forEach(stat -> pokemon.setIV(stat, average));
            return;
        }

        // 按种族值从高到低排序
        List<Map.Entry<Stat, Integer>> sortedEntries = new ArrayList<>(baseStats.entrySet());
        sortedEntries.sort((a, b) -> Integer.compare(b.getValue(), a.getValue()));

        int remaining = EVs.MAX_TOTAL_VALUE;

        // 分配剩余部分
        for (Map.Entry<Stat, Integer> entry : sortedEntries) {
            int value = Math.min(remaining, EVs.MAX_STAT_VALUE);
            remaining -= value;
            pokemon.setEV(entry.getKey(), value);
        }
    }

    public Nature suggestNature() {
        HashMap<Stat, Integer> baseStats = pokemon.getSpecies().getBaseStats();

        Map.Entry<Stat, Integer> maxEntry = null;
        Map.Entry<Stat, Integer> minEntry = null;

        for (Map.Entry<Stat, Integer> entry : baseStats.entrySet()) {
            if (entry.getKey() == Stats.HP) continue;
            if (maxEntry == null || entry.getValue() > maxEntry.getValue()) maxEntry = entry;
            if (minEntry == null || entry.getValue() < minEntry.getValue()) minEntry = entry;
        }

        if (maxEntry == null) return null;

        final Stat maxStat = maxEntry.getKey();
        final Stat minStat = minEntry.getKey();

        return Natures.INSTANCE.all().stream()
                .filter(nature -> nature.getIncreasedStat() == maxStat && nature.getDecreasedStat() == minStat)
                .findFirst()
                .orElse(null);
    }

}
